from pathlib import Path
import bencode
import json
import os

# 头部的?和/是防止与用户目录名重复
TORRENTS_KEY = '?/torrents'
LOCATE_KEY = '?/locate'
ID_KEY = '?/id'

def infinite_increment():
    num = 0
    while True:
        yield num
        num += 1

##########################################################
# directory tree
##########################################################
class Directory():
    def __init__(self):
        """
        """
        # directory hierarchy tree diagram
        self.tree = {}
        self.dir_dict = {} # {'id': tree}
        self.prev = {} # {cur_id: prev_id}
        self.inc_id = infinite_increment()

    def create_new_directory(self):
        """
        DIRECTORY_TEMPLATE = {
            ID_KEY: 1,
            TORRENTS_KEY: None, # None or [TORRENT_TEMPLATE]
            LOCATE_KEY: '', # current directory path
            # 'next_dir_name': DIRECTORY_TEMPLATE,
        }
        """
        return {
            ID_KEY: next(self.inc_id),
            TORRENTS_KEY: None, # None or []
        }

    def create_new_torrent(self, hash:str):
        """
        TORRENT_TEMPLATE = {
            ID_KEY: 1,
            LOCATE_KEY: '', # current directory path
            "hash": ''
        }
        """
        return {
            ID_KEY: next(self.inc_id),
            "hash": hash
        }
    
    def locate(self, id:int):
        """
        return 
        ------

        locate of directory by directory id or torrent id.

        current dirctory dict object

        prev directory object.
        """
        if id not in self.prev or self.prev.get(id) is None or self.prev.get(id) == id:
            return {self.dir_dict[id][LOCATE_KEY], self.dir_dict[id], None}
        
        prev_path, prev_directory, _ = Path(self.locate(self.prev.get[id]))
        cur_path = str(prev_path / self.dir_dict[id][LOCATE_KEY])
        return cur_path, self.dir_dict[id], prev_directory

    
    def get(self, path:Path|str, auto_create_dir:bool=False)->tuple[dict,dict]:
        
        path = Path(path).resolve()

        parent, current = self.tree, self.tree
        locate = Path()
        
        for i,part in enumerate(path.parts):
            parent = current
            locate = locate / part
            if auto_create_dir and part not in current:
                current[part] = self.create_new_directory(str(locate))
            self.prev[current[part][ID_KEY]] = current[ID_KEY]
            current = current[part]
        
        return parent, current

    def add_torrent(self, path:Path|str, hash:str):
        """
        将hash添加到path目录下的种子列表中（key:`TORRENTS_KEY`）
        """
        # 获取路径的目录
        path = Path(path).resolve()
        _,dir = self.get(path, True)
        
        # 目录不存在种子列表则创建
        if TORRENTS_KEY not in dir:
            dir[TORRENTS_KEY] = []

        # 将种子信息添加到目录的种子列表中
        torrent = self.create_new_torrent(hash)
        dir[TORRENTS_KEY].append(torrent)
        self.prev[torrent[ID_KEY]] = dir[ID_KEY]
        
        return torrent[ID_KEY]
    
    def add_directory(self, path:Path|str, directory:dict):
        """
        创建并添加目录path，并填充内容directory
        """
        
        # 获取路径的目录，不存在则创建
        path = Path(path).resolve()
        parent_dir, current_dir = self.get(path, True)
        
        # 目录是刚创建，则将目录直接设置为入参directory
        if len(current_dir) == 0:
            parent_dir[path.name] = directory
        else:
            # 存在同名目录，则合并
            self.merge_directory(directory, current_dir)

        # 返回父亲目录
        return parent_dir

    def merge_directory(self, src_dir:dict, dst_dir:dict):
        # 遍历源目录，合并种子列表和子目录
        for dir_name, directory in src_dir.items():
            # 合并当前目录下的种子列表
            if dir_name == TORRENTS_KEY:
                if TORRENTS_KEY not in dst_dir:
                    dst_dir[TORRENTS_KEY] = []
                dst_dir[TORRENTS_KEY].extend(directory)
            # 合并子目录
            elif  dir_name in dst_dir:
                # 递归合并子目录
                self.merge_directory(directory, dst_dir[dir_name])
            else:
                # 不存在则添加子目录
                dst_dir[dir_name] = directory
        
        # 返回合并后的目录
        return dst_dir





##########################################################
# torrent properties
##########################################################

class Torrent:
    def __init__(self):
        self.torrents = {}
        
    def refined_fastresume(self, fastresume:dict):
        refined_info = {
            'hash': fastresume['info-hash'],
            'name': fastresume['qBt-name'],
        }
        self.torrents[fastresume['info-hash']] = refined_info
        return refined_info
    
    def load(self, fastresume_path:str, torrent_path:str)->dict:
        with open(fastresume_path, 'rb') as f:
            fastresume = bencode.decode(f.read())
        with open(torrent_path, 'rb') as f:
            torrent = bencode.decode(f.read())

        file_name = torrent['info']['name']
        byte_len = 0
        if torrent.get('info').get('length') is not None:
            byte_len = torrent['info']['length']
        else:
            for f in torrent['info']['files']:
                byte_len += f['length']

        refined_info = {
            'name': file_name,
            'hash': Path(torrent_path).stem,
            'length': byte_len,
            'fastresume': str(fastresume_path),
            'torrent': str(torrent_path),
            'file_path': fastresume['qBt-savePath'],
        }
        self.torrents[refined_info['hash']] = refined_info
        return refined_info


    def get_torrent_by_hash(self, hash:str):
        return self.torrents.get(hash)

##########################################################
# modify history
##########################################################
class History:
    def __init__(self):
        # fastresume of torrent save path modification history
        # format => {torrent_hash: (old save path, new save path), ...}
        self.modify_history = {}

    def get_all_torrent_path(self, directory:dict)->dict:
        torrent_path = {}

        for dir_name,dir in directory.items():
            if isinstance(dir, dict):
                torrent_path = self.get_all_torrent_path(dir)
        
        if TORRENTS_KEY in directory:
            for hash in directory[TORRENTS_KEY]:
                torrent_path[hash] = directory[LOCATE_KEY]

        return torrent_path
    
        
    def modify_torrent_history(self, hash:str, src:Path, dst:Path):
        """记录移动操作"""
        # 如果已经记录过，则获取最早的源路径
        if hash in self.modify_history:
            src, dst = self.modify_history[hash]
        
        self.modify_history[hash] = (src, dst)

    def modify_directory_history(self, directory:dict, src:Path, desc:Path):
        """记录目录下（包括子目录）所有种子的移动记录"""
        for sub_dir_name, item in directory.items():
            # 记录当前目录的种子列表的移动记录
            if sub_dir_name == TORRENTS_KEY:
                for hash in item:
                    self.modify_torrent_history(hash, src, desc)
                continue
            # 递归遍历子目录
            self.modify_directory_history(item, src/sub_dir_name, desc/sub_dir_name)

##########################################################
# context
##########################################################
class Context:
    def load(self, bt_backup:Path|str):
        import os
        from tqdm import tqdm
        # 获取BT_backup下所有的fastresume文件的绝对路径
        file_list = os.listdir(bt_backup)
        bt_backup = Path(bt_backup).resolve()
        
        # 获取fastresume、torrent文件的绝对路径（需要保证每个fastresume都有对应的torrent存在）
        file_list = [x for x in file_list if x.endswith('.fastresume') or x.endswith('.torrent')]
        ordered_file_list = sorted(file_list)
        fastresume_list = [bt_backup / x for x in ordered_file_list[::2]]
        torrent_list = [bt_backup / x for x in ordered_file_list[1::2]]

        # 构建目录树
        directory,torrent = Directory(), Torrent()
        for i in tqdm(range(len(fastresume_list)), desc='build directory tree'):
            info_dict = torrent.load(fastresume_list[i], torrent_list[i])
            directory.add_torrent(info_dict['file_path'], info_dict['hash'])

        self.directory = directory
        self.torrent = torrent
        self.history = History()
        print('prepare complete.')

    def mv_directory(self, src:Path|str, dst:Path|str):
        """
        移动目录内容，如果src的最后一级目录名和dst最后一级目录名相同，则将src的目录内容移动到dst中。

        如果src的最后一级目录名和dst最后一级目录名不相同，则将src的目录内容移动到dst的子目录中，
        
        子目录名是src的最后一级目录名。

        过程中不存在的目录将自动创建，仅对dst创建。
        """

        # get directory
        src = Path(src).resolve()
        dst = Path(dst).resolve()
        src_parent_dir,src_dir = self.directory.get(src)

        # move to dst directory
        dst = dst if src.name == dst.name else dst/src.name
        self.directory.add_directory(dst, src_dir)
        # remove from src parent dirctory
        src_parent_dir.pop(src.name)

        # recorded in modify dict for rollback
        self.history.modify_directory_history(src_dir, src, dst)

        print(f'mv {src} {dst}')

    def mv_torrent(self, src:Path|str, dst:Path|str, hash:str):
        """
        移动种子文件，并记录移动操作。

        将src路径下的指定hash的种子文件移动到dst路径下。

        """
        #  get directory
        src = Path(src).resolve()
        dst = Path(dst).resolve()
        _,src_dir = self.directory.get(src)

        # added to dst_dir and remove from src_dir
        self.directory.add_torrent(dst, hash)
        src_dir[TORRENTS_KEY].remove(hash)

        # recorded in modify dict for rollback
        self.history.modify_torrent_history(hash, src, dst)
        
        print(f'mv {src}[{hash}] {dst}[{hash}]')

    def write_json(self):
        write_json(self.directory.tree, './output/directory.json')
        write_json(self.torrent.torrents, './output/torrent.json')
        write_json(self.history.modify_history, './output/modify_history.json')
        print('successful written to json')

def write_json(value, file_path:Path|str):
    import time
    path = Path(file_path)
    if not str.endswith(str(path), '.json'):
        path = path / f'temp_{int(time.time())}.json'
    
    if not path.parent.exists():
        path.parent.mkdir(parents=True)
    elif os.path.exists(str(path)):
        os.remove(str(path))
    
    with open(path, 'wb') as f:
        f.write(json.dumps(value, ensure_ascii=False).encode('utf-8'))

def test1():
    file_path = r'.\BT_backup\0b67efc7464644eba4b3b23f6198ffaffe639487.fastresume'
    _path = Path(file_path)
    absolute_path = _path.resolve()
    path2 = Path('D:\\l60085932\\proj\\qbittorrent\\BT_backup\\0b67efc7464644eba4b3b23f6198ffaffe639487.fastresume')
    absolute_path2 = path2.resolve()

    with open(file_path, 'rb') as f:
        t = bencode.decode(f.read())
    byte_len = 0
    for f in t['info']['files']:
        byte_len += f['length']
    import math

    unit = ['B','KB','MB','GB',"TB","PB","EB"]
    i = int(math.log(byte_len, 1024))
    print(f'{byte_len / (1024 ** i):.2f}{unit[i]}')
    print(f'{byte_len}B')
    print(f'{byte_len / 1024:.2f}KB')
    print(f'{byte_len / 1024 / 1024:.2f}MB')
    print(f'{byte_len / 1024 / 1024 / 1024:.2f}GB')

if  __name__ == '__main__':

    app_context = Context()
    app_context.load('./output/8000')

    app_context.write_json()
    
    print('done.')